#!/usr/bin/env python
import sys
import os
from killerbee import *

def usage():
    print >>sys.stderr, """
zbdsniff: Decode plaintext key ZigBee delivery from a capture file.  Will
process libpcap or Daintree SNA capture files.    jwright@willhackforsushi.com

Usage: zbdsniff [capturefiles ...]
    """

def getnetworkkey(packet):
    """
    Look for the presence of the APS Transport Key command, revealing the
    network key value.
    """

    try:
        zmac = Dot154PacketParser()
        znwk = ZigBeeNWKPacketParser()
        zaps = ZigBeeAPSPacketParser()
    
        # Process MAC layer details
        zmacpayload = zmac.pktchop(packet)[-1]
        if zmacpayload == None:
            return

        # Process NWK layer details
        znwkpayload = znwk.pktchop(zmacpayload)[-1]
        if znwkpayload == None:
            return

        # Process the APS layer details
        zapschop = zaps.pktchop(znwkpayload)
        if zapschop == None:
            return

        # See if this is an APS Command frame
        apsfc = ord(zapschop[0])
        if (apsfc & ZBEE_APS_FCF_FRAME_TYPE) != ZBEE_APS_FCF_CMD:
            return

        # Delivery mode is Normal Delivery (0)
        apsdeliverymode = (apsfc & ZBEE_APS_FCF_DELIVERY_MODE) >> 2
        if apsdeliverymode != 0:
            return

        # Ensure Security is Disabled
        if (apsfc & ZBEE_APS_FCF_SECURITY) == 1:
            return

        zapspayload = zapschop[-1]

        # Check payload length, must be at least 35 bytes
        # APS cmd | key type | key | sequence number | dest addr | src addr
        if len(zapspayload) < 35:
            return

        # Check for APS command identifier Transport Key (0x05)
        if ord(zapspayload[0]) != 5:
            return

        # Transport Key Frame, get the key type.  Network Key is 0x01, no
        # other keys should be sent in plaintext
        if ord(zapspayload[1]) != 1:
            print "Possible key or false positive?"
            return

        # Reverse these fields
        networkkey = zapspayload[2:18][::-1]
        destaddr = zapspayload[19:27][::-1]
        srcaddr = zapspayload[27:35][::-1]

        print "NETWORK KEY FOUND: ",
        for x in networkkey[0:15]:
            sys.stdout.write("%02x:"%ord(x))
        print "%02x" % ord(networkkey[15])

        print "  Destination MAC Address: ",
        for x in destaddr[0:7]:
            sys.stdout.write("%02x:"%ord(x))
        print "%02x" % ord(destaddr[7])

        print "  Source MAC Address:      ",
        for x in srcaddr[0:7]:
            sys.stdout.write("%02x:"%ord(x))
        print "%02x" % ord(srcaddr[7])

    except Exception, e:
        #print e
        return


if len(sys.argv) == 1:
    usage()
    sys.exit(0)

files = []
filecount = 0

while len(sys.argv) > 1:
    files.append(sys.argv.pop(1))

while len(files) != 0:
    file = files.pop()
    print "Processing %s"%file
    if not os.path.exists(file):
        print >>sys.stderr, "ERROR: Input file \"%s\" does not exist." % file
        continue

    filecount += 1

    # Check if the input file is libpcap; if not, assume SNA.
    cap = None
    try:
        pr = PcapReader(file)
    except Exception, e:
        if e.args == ('Unsupported pcap header format or version',):
            # Input file was not pcap, open it as SNA
            cap = DainTreeReader(file)
    
    # Following exception
    if cap == None:
            cap = pr
    
    packetcount = 0
    while 1:
        packet = cap.pnext()
        if packet[1] == None:
            # End of capture
            break
        # Add additional key/password/interesting-stuff here
        getnetworkkey(packet[1])
        
    cap.close()

print "Processed %d capture files." % filecount
